package org.springbus.ff.animate;

import org.springbus.ff.conf.FFBaseConf;
import org.springbus.ff.filter.Filter;
import org.springbus.ff.node.FFNode;
import org.springbus.ff.util.Utils;

import java.util.ArrayList;
import java.util.List;

import static org.springbus.ff.animate.AlphaReqFilter.mergeIntoNewAlphaFilter;
import static org.springbus.ff.animate.AlphaReqFilter.replaceAlphaFilter;
import static org.springbus.ff.animate.MoveRepFilter.mergeIntoNewOverflyFilter;
import static org.springbus.ff.animate.MoveRepFilter.replaceOverflyFilter;
import static org.springbus.ff.animate.RotateRepFilter.mergeIntoNewRotateFilter;
import static org.springbus.ff.animate.RotateRepFilter.replaceRotateFilter;
import static org.springbus.ff.animate.ZoomRepFilter.replaceZoomFilter;

public class FFAnimations {
    private List<FFAnimation> list;
    private FFNode target;

    public FFAnimations(List<FFAnimation> animations) {
        list = new ArrayList<>();
        setAnimations(animations);
    }


    private FFAnimation replaceFadeToAlphaByText(FFAnimation animation) {

        if (this.target.type.equals("text")) {
            return animation;
        }
        if (!animation.isFFAni) {
            if (animation.type.equals("fade") || animation.type.equals("show")) {
                animation.type = "alpha";
            }
        } else {
            if (animation.type.equals("fade") || animation.type.equals("show")) {
                animation.type = "alpha";
            }
        }

        return animation;
    }

    public void setTarget(FFNode ffNode) {
        this.target = ffNode;
    }


    /**
     * Set animations array
     *
     * @param {Array} animations - animations array
     * @public
     */
    public void setAnimations(List<FFAnimation> animations) {
        for (FFAnimation ani : animations) {
            this.addAnimate(ani);
        }
    }

    /**
     * add default effect animation
     *
     * @param {String} type - effect type
     * @param {Number} time - effect time
     * @param {Number} delay - effect delay
     * @public
     */
    public void addEffect(String type, Float time, Float delay) {
        FFBaseConf conf = null;
        if (time == null) {
            conf = new FFBaseConf();
            type = conf.getType();
            time = conf.getTime();
            delay = conf.getDelay();
        } else {
            conf = new FFBaseConf(type, time, delay);
        }
        List<FFBaseConf> params = Effects.effects.get(type);
        if (null == params) return;

        for (FFBaseConf cf : params) {
            cf.setTime(time);
            cf.setDelay(delay);
            FFAnimation p = new FFAnimation(cf);
            this.addAnimate(p);
        }
    }

    public FFAnimation addAnimate(FFAnimation animation) {
        this.replaceFadeToAlphaByText(animation);

        if (!animation.isFFAni) {
            animation = new FFAnimation(animation.conf);
        }
        this.list.add(animation);
        animation.parent = this.target;
        return animation;
    }


    public boolean hasAnimate(String itype) {
        boolean has = false;

        for (FFAnimation ani : this.list) {
            String type = ani.type;
            if (type.equals(itype)) {
                has = true;
                break;
            }
        }

        return has;
    }

    public Boolean hasZoompanPad() {
        boolean has = false;
        for (FFAnimation ani : this.list) {
            String type = ani.conf.getType();
            boolean pad = ani.conf.isPad();
            if (type.equals("zoom") && pad) {
                has = true;
                break;
            }
        }

        return has;
    }

    /**
     * get max scale value from scale conf
     *
     * @private
     */
    public double getMaxScale() {
        double maxScale = 1;
        for (FFAnimation ani : this.list) {
            boolean pad = ani.conf.isPad();
            String type = ani.conf.getType();
            if (type.equals("zoom") && pad) {
                double from = ani.getFromTo().from;
                double to = ani.getFromTo().to;
                maxScale = Math.max(from, to);
            }
        }
        return maxScale;
    }

    /**
     * concat all animation filters
     *
     * @private
     */
    public List<AniFilter> concatFilters() {
        List<AniFilter> filters = this.mergeListFilters();
        // Filter order is very important
        // rotate -> zoompan -> fade -> overlay
        filters = this.mergeSpecialFilters("alpha", filters);
        filters = this.mergeSpecialFilters("rotate", filters);
        filters = this.mergeSpecialFilters("zoompan", filters);
        filters = this.swapFadeFilterPosition(filters);
        filters = this.mergeSpecialFilters("overlay", filters);
        filters = this.convertToFFmpegFilter(filters);

        return filters;
    }

    public List<AniFilter> convertToFFmpegFilter(List<AniFilter> filters) {
        List<AniFilter> cfilters = new ArrayList<>();
        for (AniFilter filter : filters) {
            cfilters.add(filter);
        }
        return cfilters;
    }

    public void replaceEffectConfVal() {
        for (FFAnimation ani : this.list) {
            FFBaseConf conf = ani.conf;
            // for (let key in conf){
            //const val = conf[key];
             //const newVal = Effects.mapping(val, this.target);
            // conf[key] = newVal == = null ? val : newVal;
        }
    }


    public List<AniFilter> swapFadeFilterPosition(List<AniFilter> filters) {
        List<AniFilter> cfilters = new ArrayList<>(filters);
        List<AniFilter> tfilters = new ArrayList<>();

        // 1. filter and delete all overlay
        for (AniFilter aniFilter : filters) {
            if (aniFilter.getName().equals("fade")) {
                tfilters.add(aniFilter);
                Utils.deleteArrayElement(cfilters, aniFilter);
            }
        }

        cfilters.addAll(tfilters);
        return cfilters;
    }

    public List<AniFilter> mergeListFilters() {
        List<AniFilter> filters = new ArrayList<>();
        for (FFAnimation ani : this.list) {
            AniFilter filter = ani.toFilter();
            filters.add(filter);
        }
        return filters;
    }

    /**
     * merge any specia filters
     *
     * @private
     */
    public List<AniFilter> mergeSpecialFilters(String type, List<AniFilter> filters) {
        List<AniFilter> cfilters = new ArrayList<>(filters);
        List<AniFilter> tfilters = new ArrayList<>();

        // 1. filter and delete all overlay
        // forEach(filters, aniFilter => {
        for (AniFilter aniFilter : filters) {
            if (aniFilter.getName().equals(type)) {
                tfilters.add(aniFilter);
                Utils.deleteArrayElement(cfilters, aniFilter);
            }
        }

        // 2-1. if only one push all overlay to last
        // [filter] -> [cfilters ...filter(last)]
        if (tfilters.size() == 1) {
            AniFilter aniFilter = tfilters.get(0);
            switch (type) {
                case "zoompan":
                    replaceZoomFilter(aniFilter);
                    break;
                case "overlay":
                    replaceOverflyFilter(aniFilter);
                    break;
                case "rotate":
                    replaceRotateFilter(aniFilter);
                    break;
                case "alpha":
                    replaceAlphaFilter(aniFilter);
                    break;
            }

            cfilters.addAll(tfilters);
        }

        // 2-2 if more than one merge all tfilters
        // 1. [filter-in, filter-out] -> [cfilters ...filter-in, filter-out(last)]
        else if (tfilters.size() > 1) {
            tfilters = Utils.sortArrayByKey(tfilters, "showType", "in");
            AniFilter newFilter = null;
            switch (type) {
                case "zoompan":
                    newFilter = ZoomRepFilter.mergeIntoNewZoompanFilter(tfilters);
                    break;
                case "overlay":
                    newFilter = mergeIntoNewOverflyFilter(tfilters);
                    break;
                case "rotate":
                    newFilter = mergeIntoNewRotateFilter(tfilters);
                    break;
                case "alpha":
                    newFilter = mergeIntoNewAlphaFilter(tfilters);
                    break;
            }
            if (newFilter != null) {
                cfilters.add(newFilter);
            }
        }

        return cfilters;
    }


    /**
     * get duration from animations
     *
     * @private
     */
    public Float getDuration() {
        float duration = (float) 0f;
        for (FFAnimation ani : list) {
            String name = ani.conf.getName();
            Float time = ani.time;
            Float delay = ani.delay;
            String showType = ani.showType;

            if (showType.equals("out") || name.equals("no")) {
                duration = time + delay;
            }
        }
        return duration;
    }

    /**
     * modify delay time Less appearTime
     *
     * @private
     */
    public void modifyDelayTime(int appearTime) {
        for (FFAnimation ani : list) {
            Float delay = ani.conf.getDelay();
            delay -= appearTime;
            ani.conf.setDelay(Math.max(delay, 0));
        }
    }

    /**
     * Get appearTime from animations
     *
     * @private
     */
    public Float getAppearTime() {
        Float appearTime = 0f;
        for (FFAnimation ani : list) {
            String showType = ani.conf.getShowType();
            Float delay = ani.conf.getDelay();
            if (showType.equals("in")) {
                if (delay != null) {
                    appearTime = delay;
                } else {
                    appearTime = 0f;
                }
            }
        }
        return appearTime;
    }
}
